{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "02_Python",
      "provenance": [],
      "collapsed_sections": [],
      "toc_visible": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "D-ndUm1CvVEZ"
      },
      "source": [
        "<div align=\"center\">\n",
        "<h1><img width=\"30\" src=\"https://madewithml.com/static/images/rounded_logo.png\">&nbsp;<a href=\"https://madewithml.com/\">Made With ML</a></h1>\n",
        "Applied ML · MLOps · Production\n",
        "<br>\n",
        "Join 30K+ developers in learning how to responsibly <a href=\"https://madewithml.com/about/\">deliver value</a> with ML.\n",
        "    <br>\n",
        "</div>\n",
        "\n",
        "<br>\n",
        "\n",
        "<div align=\"center\">\n",
        "    <a target=\"_blank\" href=\"https://newsletter.madewithml.com\"><img src=\"https://img.shields.io/badge/Subscribe-30K-brightgreen\"></a>&nbsp;\n",
        "    <a target=\"_blank\" href=\"https://github.com/GokuMohandas/MadeWithML\"><img src=\"https://img.shields.io/github/stars/GokuMohandas/MadeWithML.svg?style=social&label=Star\"></a>&nbsp;\n",
        "    <a target=\"_blank\" href=\"https://www.linkedin.com/in/goku\"><img src=\"https://img.shields.io/badge/style--5eba00.svg?label=LinkedIn&logo=linkedin&style=social\"></a>&nbsp;\n",
        "    <a target=\"_blank\" href=\"https://twitter.com/GokuMohandas\"><img src=\"https://img.shields.io/twitter/follow/GokuMohandas.svg?label=Follow&style=social\"></a>\n",
        "    <br>\n",
        "    🔥&nbsp; Among the <a href=\"https://github.com/topics/deep-learning\" target=\"_blank\">top ML</a> repositories on GitHub\n",
        "</div>\n",
        "\n",
        "<br>\n",
        "<hr>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pY8iWjayaC22"
      },
      "source": [
        "# Python\n",
        "\n",
        "In this lessons, we'll learn about the basics of Python programmming. "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "gN-UI0ZUd0Xt"
      },
      "source": [
        "<div align=\"left\">\n",
        "<a target=\"_blank\" href=\"https://madewithml.com/courses/foundations/python/\"><img src=\"https://img.shields.io/badge/📖 Read-blog post-9cf\"></a>&nbsp;\n",
        "<a href=\"https://github.com/GokuMohandas/MadeWithML/blob/main/notebooks/02_Python.ipynb\" role=\"button\"><img src=\"https://img.shields.io/static/v1?label=&amp;message=View%20On%20GitHub&amp;color=586069&amp;logo=github&amp;labelColor=2f363d\"></a>&nbsp;\n",
        "<a href=\"https://colab.research.google.com/github/GokuMohandas/MadeWithML/blob/main/notebooks/02_Python.ipynb\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"></a>\n",
        "</div>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "VoMq0eFRvugb"
      },
      "source": [
        "#  Variables"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "qWro5T5qTJJL"
      },
      "source": [
        "Variables are containers for holding data and they're defined by a name and value.\n",
        "\n",
        "<div align=\"left\">\n",
        "<img src=\"https://raw.githubusercontent.com/GokuMohandas/MadeWithML/main/images/foundations/python/variables.png\" width=\"220\">\n",
        "</div>"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "0-dXQiLlTIgz",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "a442b3e3-86e2-4328-b212-b61f7167be19"
      },
      "source": [
        "# Integer variable\n",
        "x = 5\n",
        "print (x)\n",
        "print (type(x))\n"
      ],
      "execution_count": 1,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "5\n",
            "<class 'int'>\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LDAdgPSCyMbN"
      },
      "source": [
        "We can change the value of a variable by simply assigning a new value to it."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "5Ym0owFxTkjo",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "850eb0fd-6700-42a1-8087-920a7f68f8a6"
      },
      "source": [
        "# String variable\n",
        "x = \"hello\"\n",
        "print (x)\n",
        "print (type(x))"
      ],
      "execution_count": 2,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "hello\n",
            "<class 'str'>\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Lsq_c_AhEzZN"
      },
      "source": [
        "There are many different types of variables: integers, floats, strings, boolean etc."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "m4kAmbD9EzHz",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "aad7af89-2397-4968-c9aa-456587691b6d"
      },
      "source": [
        "# int variable\n",
        "x = 5\n",
        "print (x, type(x))"
      ],
      "execution_count": 3,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "5 <class 'int'>\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "gb9BzIoOEy6A",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "1bf6df26-ca01-41e0-800c-a3f6e833fe9a"
      },
      "source": [
        "# float variable\n",
        "x = 5.0\n",
        "print (x, type(x))"
      ],
      "execution_count": 4,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "5.0 <class 'float'>\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "RkqP-eraEytN",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "d38eeca5-d2b5-46f4-c506-e4de78b529f6"
      },
      "source": [
        "# text variable\n",
        "x = \"5\" \n",
        "print (x, type(x))"
      ],
      "execution_count": 5,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "5 <class 'str'>\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ws4HSsJpEyfL",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "c9e11f18-d979-4ab5-9c20-b58e1bd2e439"
      },
      "source": [
        "# boolean variable\n",
        "x = True\n",
        "print (x, type(x))"
      ],
      "execution_count": 6,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "True <class 'bool'>\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EWOmrbiDybhF"
      },
      "source": [
        "We can also do operations with variables."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "1a4ZhMV1T1-0",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "99e2f78b-79b1-4793-e153-8fbbfb316362"
      },
      "source": [
        "# Variables can be used with each other\n",
        "a = 1\n",
        "b = 2\n",
        "c = a + b\n",
        "print (c)"
      ],
      "execution_count": 7,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "3\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6HPtavfdU8Ut"
      },
      "source": [
        "We should always know what types of variables we're dealing with so we can do the right operations with them. Here's a common mistake that can happen if we're using the wrong variable type."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "8pr1-i7IVD-h",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "aec92c75-17e8-4b63-aa5c-1185ee515c1b"
      },
      "source": [
        "# int variables\n",
        "a = 5\n",
        "b = 3\n",
        "print (a + b)"
      ],
      "execution_count": 8,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "8\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "A9u0Mtke5xkk",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "378e4bdb-05af-4612-b4d9-64aba08a13bf"
      },
      "source": [
        "# string variables\n",
        "a = \"5\"\n",
        "b = \"3\"\n",
        "print (a + b)"
      ],
      "execution_count": 9,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "53\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "q4R_UF6PVw4V"
      },
      "source": [
        "#  Lists"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LvGsQBj4VjMl"
      },
      "source": [
        "Lists are an ordered, mutable (changeable) collection of values that are *comma separated* and enclosed by *square brackets*. A list can be comprised of many different types of variables (below is a list with an integer, string and a float)."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "9iPESkq9VvlX",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "90a76b58-d1ed-413e-ec07-1520102e4d19"
      },
      "source": [
        "# Creating a list\n",
        "x = [3, \"hello\", 1.2]\n",
        "print (x)"
      ],
      "execution_count": 10,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "[3, 'hello', 1.2]\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "esDwOfutKtBf",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "193be420-f634-4ab3-caef-dff538cf8a73"
      },
      "source": [
        "# Length of a list\n",
        "len(x)"
      ],
      "execution_count": 11,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "3"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 11
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0xC6WvuwbGDg"
      },
      "source": [
        "You can add to a list by using the **append** function."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "7lbajc-zV515",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "9014c8b6-fcf5-4cff-d401-db8b81e49be4"
      },
      "source": [
        "# Adding to a list\n",
        "x.append(7)\n",
        "print (x)\n",
        "print (len(x))"
      ],
      "execution_count": 12,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "[3, 'hello', 1.2, 7]\n",
            "4\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "TjVjmiSIK1YA",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "18436f6f-3f7b-4855-b12d-a428cfd3e883"
      },
      "source": [
        "# Replacing items in a list\n",
        "x[1] = \"bye\"\n",
        "print (x)"
      ],
      "execution_count": 13,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "[3, 'bye', 1.2, 7]\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "tNLaysG6K6E9",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "6690d3a1-11de-4296-c3f5-6b4f18bfe6ff"
      },
      "source": [
        "# Operations\n",
        "y = [2.4, \"world\"]\n",
        "z = x + y\n",
        "print (z)"
      ],
      "execution_count": 14,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "[3, 'bye', 1.2, 7, 2.4, 'world']\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "M7mQpE7dtpX6"
      },
      "source": [
        "# Indexing and Slicing"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wJqJM2Q13TwE"
      },
      "source": [
        "Indexing and slicing from lists allow us to retrieve specific values within lists. Note that indices can be positive (starting from 0) or negative (-1 and lower, where -1 is the last item in the list).\n",
        "\n",
        "<div align=\"left\">\n",
        "<img src=\"https://raw.githubusercontent.com/GokuMohandas/MadeWithML/main/images/foundations/python/indexing.png\" width=\"300\">\n",
        "</div>"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "W0xpIryJWCN9",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "9c829bac-17e0-4e96-decb-053e73759ee7"
      },
      "source": [
        "# Indexing\n",
        "x = [3, \"hello\", 1.2]\n",
        "print (\"x[0]: \", x[0])\n",
        "print (\"x[1]: \", x[1])\n",
        "print (\"x[-1]: \", x[-1]) # the last item\n",
        "print (\"x[-2]: \", x[-2]) # the second to last item"
      ],
      "execution_count": 15,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "x[0]:  3\n",
            "x[1]:  hello\n",
            "x[-1]:  1.2\n",
            "x[-2]:  hello\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "VSu_HNrnc1WK",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "4f709a62-e4da-4a6a-c0d0-f27a2ed2e834"
      },
      "source": [
        "# Slicing\n",
        "print (\"x[:]: \", x[:]) # all indices\n",
        "print (\"x[1:]: \", x[1:]) # index 1 to the end of the list\n",
        "print (\"x[1:2]: \", x[1:2]) # index 1 to index 2 (not including index 2)\n",
        "print (\"x[:-1]: \", x[:-1]) # index 0 to last index (not including last index)"
      ],
      "execution_count": 16,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "x[:]:  [3, 'hello', 1.2]\n",
            "x[1:]:  ['hello', 1.2]\n",
            "x[1:2]:  ['hello']\n",
            "x[:-1]:  [3, 'hello']\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ddpIO6LLVzh0"
      },
      "source": [
        "# Tuples"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CAZblq7oXY3s"
      },
      "source": [
        "Tuples are collections that are ordered and immutable (unchangeable). You will use these to store values that will never be changed."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "G95lu8xWXY90",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "ac6a5fb2-d052-42fc-e70c-0bd375f3bc80"
      },
      "source": [
        "# Creating a tuple\n",
        "x = (3.0, \"hello\") # tuples start and end with ()\n",
        "print (x)"
      ],
      "execution_count": 17,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "(3.0, 'hello')\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "kq23Bej1acAP",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "f632c8ea-c60e-4602-fc51-9ab8bb8ff210"
      },
      "source": [
        "# Adding values to a tuple\n",
        "x = x + (5.6, 4)\n",
        "print (x)"
      ],
      "execution_count": 18,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "(3.0, 'hello', 5.6, 4)\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "-o-tW98FLffc"
      },
      "source": [
        "# Try to change (it won't work and you'll get an error)\n",
        "# x[0] = 1.2"
      ],
      "execution_count": 19,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UdlJHkwZV3Mz"
      },
      "source": [
        "# Dictionaries"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "azp3AoxYXS26"
      },
      "source": [
        "Dictionaries are an unordered, mutable and indexed collection of key-value pairs. You can retrieve values based on the key and a dictionary cannot have two of the same keys. \n",
        "\n",
        "<div align=\"left\">\n",
        "<img src=\"https://raw.githubusercontent.com/GokuMohandas/MadeWithML/main/images/foundations/python/dictionaries.png\" width=\"320\">\n",
        "</div>"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "pXhNLbzpXXSk",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "3026289c-8b82-4a6c-98c5-5e768cc9f8f4"
      },
      "source": [
        "# Creating a dictionary\n",
        "person = {'name': 'Goku',\n",
        "          'eye_color': 'brown'}\n",
        "print (person)\n",
        "print (person['name'])\n",
        "print (person['eye_color'])"
      ],
      "execution_count": 20,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "{'name': 'Goku', 'eye_color': 'brown'}\n",
            "Goku\n",
            "brown\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "1HXtX8vQYjXa",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "c7c7596e-3f69-415f-ad3f-307aa03103da"
      },
      "source": [
        "# Changing the value for a key\n",
        "person['eye_color'] = 'green'\n",
        "print (person)"
      ],
      "execution_count": 21,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "{'name': 'Goku', 'eye_color': 'green'}\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "qn33iB0MY5dT",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "90e146e0-7e77-4a39-803e-1248bb567bb3"
      },
      "source": [
        "# Adding new key-value pairs\n",
        "person['age'] = 24\n",
        "print (person)"
      ],
      "execution_count": 22,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "{'name': 'Goku', 'eye_color': 'green', 'age': 24}\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "g9EYmzMKa9YV",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "a60ba8db-d25c-4452-9051-fa2ea7fb4ac6"
      },
      "source": [
        "# Length of a dictionary\n",
        "print (len(person))"
      ],
      "execution_count": 23,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "3\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "B-DInx_Xo2vJ"
      },
      "source": [
        "# If statements"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ZG_ICGRGo4tY"
      },
      "source": [
        "We can use `if` statements to conditionally do something. The conditions are defined by the words `if`, `elif` (which stands for else if) and `else`. We can have as many `elif` statements as we want. The indented code below each condition is the code that will execute if the condition is `True`."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "uob9lQuKo4Pg",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "66adff4a-b673-4b62-fe77-698f6c61baa5"
      },
      "source": [
        "# If statement\n",
        "x = 4\n",
        "if x < 1:\n",
        "    score = 'low'\n",
        "elif x <= 4: # elif = else if\n",
        "    score = 'medium'\n",
        "else:\n",
        "    score = 'high'\n",
        "print (score)"
      ],
      "execution_count": 24,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "medium\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "vwsQaZqIpfJ3",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "dffe699e-0bb6-4e9c-9f67-0f7342100ae9"
      },
      "source": [
        "# If statement with a boolean\n",
        "x = True\n",
        "if x:\n",
        "    print (\"it worked\")"
      ],
      "execution_count": 25,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "it worked\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sJ7NPGEKV6Ik"
      },
      "source": [
        "# Loops"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cRgqRyfITuEC"
      },
      "source": [
        "### For Loops"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YRVxhVCkn0vc"
      },
      "source": [
        "A `for` loop can iterate over a collection of values (lists, tuples, dictionaries, etc.) The indented code is executed for each item in the collection of values."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "OB5PtyqAn8mj",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "c4c87da2-896a-4dcf-b5b6-db63b540bade"
      },
      "source": [
        "# For loop\n",
        "veggies = [\"carrots\", \"broccoli\", \"beans\"]\n",
        "for veggie in veggies:\n",
        "    print (veggie)"
      ],
      "execution_count": 26,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "carrots\n",
            "broccoli\n",
            "beans\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PQc56LtEDuX2"
      },
      "source": [
        "When the loop encounters the `break` command, the loop will terminate immediately. If there were more items in the list, they will not be processed."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "6XyhCrFeoGj4",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "a6589ff6-c2e7-432d-e3a0-faf3e363dcb9"
      },
      "source": [
        "# `break` from a for loop\n",
        "veggies = [\"carrots\", \"broccoli\", \"beans\"]\n",
        "for veggie in veggies:\n",
        "    if veggie == \"broccoli\":\n",
        "        break\n",
        "    print (veggie)"
      ],
      "execution_count": 27,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "carrots\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "O_0-vSSuEAqU"
      },
      "source": [
        "When the loop encounters the `continue` command, the loop will skip all other operations for that item in the list only. If there were more items in the list, the loop will continue normally."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "KJvlfYs8QbQZ",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "379ea18f-7732-4496-9c3d-42d1d582eff0"
      },
      "source": [
        "# `continue` to the next iteration\n",
        "veggies = [\"carrots\", \"broccoli\", \"beans\"]\n",
        "for veggie in veggies:\n",
        "    if veggie == \"broccoli\":\n",
        "        continue\n",
        "    print (veggie)"
      ],
      "execution_count": 28,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "carrots\n",
            "beans\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "VAbnASi2TwZ9"
      },
      "source": [
        "### While Loops"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "nuHAyQdr_jUa"
      },
      "source": [
        "A `while` loop can perform repeatedly as long as a condition is `True`. We can use `continue` and `break` commands in `while` loops as well."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "5Tf2x4okp3fH",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "22de7e28-74b8-48cb-a5f9-cd5247e776c6"
      },
      "source": [
        "# While loop\n",
        "x = 3\n",
        "while x > 0:\n",
        "    x -= 1 # same as x = x - 1\n",
        "    print (x)"
      ],
      "execution_count": 29,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "2\n",
            "1\n",
            "0\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "gJw-EDO9WBL_"
      },
      "source": [
        "# Functions"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hDIOUdWCqBwa"
      },
      "source": [
        "Functions are a way to modularize reusable pieces of code. They're defined by the keyword `def` which stands for definition and they can have the following components.\n",
        "\n",
        "<div align=\"left\">\n",
        "<img src=\"https://raw.githubusercontent.com/GokuMohandas/MadeWithML/main/images/foundations/python/functions.png\" width=\"350\">\n",
        "</div>"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "iin1ZXmMqA0y"
      },
      "source": [
        "# Define the function\n",
        "def add_two(x):\n",
        "    \"\"\"Increase x by 2.\"\"\" # explains what this function will do\n",
        "    x += 2\n",
        "    return x"
      ],
      "execution_count": 30,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "OIf2dJ8RGj8l"
      },
      "source": [
        "Here are the components that may be required when we want to use the function. we need to ensure that the function name and the input parameters match with how we defined the function above.\n",
        "\n",
        "<div align=\"left\">\n",
        "<img src=\"https://raw.githubusercontent.com/GokuMohandas/MadeWithML/main/images/foundations/python/calling_functions.png\" width=\"350\">\n",
        "</div>"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "4ye1A5FobAUd",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "6ea91eb8-3ab3-4d5c-ba7f-988e7a9cba94"
      },
      "source": [
        "# Use the function\n",
        "score = 0\n",
        "new_score = add_two(x=score)\n",
        "print (new_score)"
      ],
      "execution_count": 31,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "2\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5kpEiTUZqMax"
      },
      "source": [
        "A function can have as many input parameters and outputs as we want."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "DC6x3DMrqlE3"
      },
      "source": [
        "# Function with multiple inputs\n",
        "def join_name(first_name, last_name):\n",
        "    \"\"\"Combine first name and last name.\"\"\"\n",
        "    joined_name = first_name + \" \" + last_name\n",
        "    return joined_name"
      ],
      "execution_count": 32,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ehc8bNoGbBt8",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "4aaab48c-24be-4c5b-cee6-40bbeddfca6c"
      },
      "source": [
        "# Use the function\n",
        "first_name = \"Goku\"\n",
        "last_name = \"Mohandas\"\n",
        "joined_name = join_name(first_name=first_name, \n",
        "                        last_name=last_name)\n",
        "print (joined_name)"
      ],
      "execution_count": 33,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "Goku Mohandas\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "x3_7Qv91Gjua"
      },
      "source": [
        "> It's good practice to always use keyword argument when using a function so that it's very clear what input variable belongs to what function input parameter. On a related note, you will often see the terms `*args` and `**kwargs` which stand for arguments and keyword arguments. You can extract them when they are passed into a function. The significance of the `*` is that any number of arguments and keyword arguments can be passed into the function."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "XHhaCfFaHSVo"
      },
      "source": [
        "def f(*args, **kwargs):\n",
        "    x = args[0]\n",
        "    y = kwargs.get('y')\n",
        "    print (f\"x: {x}, y: {y}\")"
      ],
      "execution_count": 34,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ZRB9msy0HmR_",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "981b0659-6da7-4488-bbc7-624c74f71583"
      },
      "source": [
        "f(5, y=2)"
      ],
      "execution_count": 35,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "x: 5, y: 2\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lBLa1n54WEd2"
      },
      "source": [
        "# Classes"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mGua8QnArAZh"
      },
      "source": [
        "Classes are object constructors and are a fundamental component of object oriented programming in Python. They are composed of a set of functions that define the class and it's operations."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CypuBBQyRv8s"
      },
      "source": [
        "### `__init__` function"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Hrx2bKTXR4E3"
      },
      "source": [
        "The `__init__` function is used when an instance of the class is initialized. "
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "DXmPwI1frAAd"
      },
      "source": [
        "# Creating the class\n",
        "class Pet(object):\n",
        "    \"\"\"Class object for a pet.\"\"\"\n",
        "    \n",
        "    def __init__(self, species, name):\n",
        "        \"\"\"Initialize a Pet.\"\"\"\n",
        "        self.species = species\n",
        "        self.name = name"
      ],
      "execution_count": 36,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "j5F4XmFQSfVr",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "27c27725-d6e3-4eb7-8018-d70ee84cdbb8"
      },
      "source": [
        "# Creating an instance of a class\n",
        "my_dog = Pet(species=\"dog\", \n",
        "             name=\"Scooby\")\n",
        "print (my_dog)\n",
        "print (my_dog.name)"
      ],
      "execution_count": 37,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "<__main__.Pet object at 0x7f7cab4cbac8>\n",
            "Scooby\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6ZVfBXNISUBR"
      },
      "source": [
        "### `__str__` function"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8q6oiWHQSqOo"
      },
      "source": [
        "The `print (my_dog)` command printed something not so relevant to us. Let's fix that with the `__str__` function."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "N1_RDVo9Ruhf"
      },
      "source": [
        "# Creating the class\n",
        "class Pet(object):\n",
        "    \"\"\"Class object for a pet.\"\"\"\n",
        "  \n",
        "    def __init__(self, species, name):\n",
        "        \"\"\"Initialize a Pet.\"\"\"\n",
        "        self.species = species\n",
        "        self.name = name\n",
        " \n",
        "    def __str__(self):\n",
        "        \"\"\"Output when printing an instance of a Pet.\"\"\"\n",
        "        return f\"{self.species} named {self.name}\""
      ],
      "execution_count": 38,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "vQeU2yScSkW7",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "6acd6978-d0f2-4661-bfcc-531e98c84ac9"
      },
      "source": [
        "# Creating an instance of a class\n",
        "my_dog = Pet(species=\"dog\", \n",
        "             name=\"Scooby\")\n",
        "print (my_dog)\n",
        "print (my_dog.name)"
      ],
      "execution_count": 39,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "dog named Scooby\n",
            "Scooby\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "luB-RBMXK_rL"
      },
      "source": [
        "> Classes can be customized with **magic** functions like `__init__` and `__str__`, to enable powerful operations. We'll be exploring additional built-in functions in subsequent notebooks (like `__len__`, `__iter__` and `__getitem__`, etc.) but if you're curious, here is a [tutorial](https://rszalski.github.io/magicmethods/) on more magic methods."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LaTaBd8jSYuy"
      },
      "source": [
        "### Object methods"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Vz95DOFjSY3s"
      },
      "source": [
        "# Creating the class\n",
        "class Pet(object):\n",
        "    \"\"\"Class object for a pet.\"\"\"\n",
        "  \n",
        "    def __init__(self, species, name):\n",
        "        \"\"\"Initialize a Pet.\"\"\"\n",
        "        self.species = species\n",
        "        self.name = name\n",
        " \n",
        "    def __str__(self):\n",
        "        \"\"\"Output when printing an instance of a Pet.\"\"\"\n",
        "        return f\"{self.species} named {self.name}\"\n",
        "        \n",
        "    def change_name(self, new_name):\n",
        "        \"\"\"Change the name of your Pet.\"\"\"\n",
        "        self.name = new_name"
      ],
      "execution_count": 40,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ezQq_Fhhrqrv",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "a7fd012b-b036-45f8-8bd0-953d09f5cd63"
      },
      "source": [
        "# Creating an instance of a class\n",
        "my_dog = Pet(species=\"dog\", name=\"Scooby\")\n",
        "print (my_dog)\n",
        "print (my_dog.name)"
      ],
      "execution_count": 41,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "dog named Scooby\n",
            "Scooby\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "qTinlRj1szc5",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "3933c9ad-6d4d-44c9-8bc3-870808684e88"
      },
      "source": [
        "# Using a class's function\n",
        "my_dog.change_name(new_name=\"Scrappy\")\n",
        "print (my_dog)\n",
        "print (my_dog.name)"
      ],
      "execution_count": 42,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "dog named Scrappy\n",
            "Scrappy\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wRcOLRK3TlEO"
      },
      "source": [
        "### Inheritance"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wR1lyaGwWAcR"
      },
      "source": [
        "Inheritance allows us to inherit all the properties and methods from another class (the parent). Notice how we inherited the initialized variables from the parent `Pet` class like species and name. We also inherited the `change_name` function. But for the `__str__` function, we define our own version to overwrite the `Pet` `__str__` function. We can similarly overwrite any object functions as well."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "TaDN2qwBTm7_"
      },
      "source": [
        "class Dog(Pet):\n",
        "    def __init__(self, name, breed):\n",
        "        super().__init__(species=\"dog\", name=name)\n",
        "        self.breed = breed\n",
        "    \n",
        "    def __str__(self):\n",
        "        return f\"{self.breed} named {self.name}\""
      ],
      "execution_count": 48,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "tKxWHokkT-RI",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "b42bd347-70bb-4cb6-dc2e-61f2ce10866b"
      },
      "source": [
        "scooby = Dog(breed=\"Great Dane\", name=\"Scooby\")\n",
        "print (scooby)"
      ],
      "execution_count": 49,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "Great Dane named Scooby\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "YLF_tw5mVFO9",
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "outputId": "c4ee09cc-48b7-43ca-c415-f833686f8aa3"
      },
      "source": [
        "scooby.change_name('Scooby Doo')\n",
        "print (scooby)"
      ],
      "execution_count": 50,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "Great Dane named Scooby Doo\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "B-XNM5_37842"
      },
      "source": [
        "### Methods"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pvqSxZn57_2-"
      },
      "source": [
        "There are two important decorator methods to know about when it comes to classes: `@classmethod` and `@staticmethod`. We'll learn about decorators in the next section below but these specific methods pertain to classes so we'll cover them here."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "fdoi3R7z8QJw"
      },
      "source": [
        "class Dog(Pet):\n",
        "    def __init__(self, name, breed):\n",
        "        super().__init__(species=\"dog\", name=name)\n",
        "        self.breed = breed\n",
        "    \n",
        "    def __str__(self):\n",
        "        return f\"{self.breed} named {self.name}\"\n",
        "\n",
        "    @classmethod\n",
        "    def from_dict(cls, d):\n",
        "        return cls(name=d[\"name\"], breed=d[\"breed\"])\n",
        "\n",
        "    @staticmethod\n",
        "    def is_cute(breed):\n",
        "        return True  # all animaals are cute!"
      ],
      "execution_count": 63,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6lXOkrAI-IuJ"
      },
      "source": [
        "A `@classmethod` allows us to create class instances by passing in the uninstantiated class itself (`cls`). This is a great way to create (or load) classes from objects (ie. dictionaries). "
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "kPgWwVBu8_ro",
        "outputId": "40a33d4e-3711-459d-ca60-43226a14db3c",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "source": [
        "# Create instance\n",
        "d = {\"name\": \"Cassie\", \"breed\": \"Border Collie\"}\n",
        "cassie = Dog.from_dict(d=d)\n",
        "print(cassie)"
      ],
      "execution_count": 64,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "Border Collie named Cassie\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LEhGQqU79-MO"
      },
      "source": [
        "A `@staticmethod` can be called from an uninstantiated class object so we can do things like this:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "9PdglAwa8_uG",
        "outputId": "2657a42b-d3f1-4f8f-a1ac-4a9767874bb6",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "source": [
        "# Static method\n",
        "Dog.is_cute(breed=\"Border Collie\")"
      ],
      "execution_count": 65,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "True"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 65
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "nv5ja3dEtfJN"
      },
      "source": [
        "# Decorators"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8aSOSxkSw8lu"
      },
      "source": [
        "Recall that functions allow us to modularize code and reuse them. However, we'll often want to add some functionality before or after the main function executes and we may want to do this for many different functions. Instead of adding more code to the original function, we can use decorators!\n",
        "\n",
        "*    **decorators**: augment a function with pre/post-processing. Decorators wrap around the main function and allow us to operate on the inputs and or outputs."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-AdOBNG7FGJB"
      },
      "source": [
        "Suppose we have a function called `operations` which increments the input value x by 1."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "B-JYWOyP1ekZ"
      },
      "source": [
        "def operations(x):\n",
        "    \"\"\"Basic operations.\"\"\"\n",
        "    x += 1\n",
        "    return x"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "XD6oY_8C1en8",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "outputId": "ff8e207d-bcca-44be-b8bd-50663a83d04d"
      },
      "source": [
        "operations(x=1)"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "2"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 47
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LbAeHmc91iq-"
      },
      "source": [
        "Now let's say we want to increment our input x by 1 before and after the `operations` function executes and, to illustrate this example, let's say the increments have to be separate steps. Here's how we would do it by changing the original code:"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "_zZMfUHq7xuv"
      },
      "source": [
        "def operations(x):\n",
        "    \"\"\"Basic operations.\"\"\"\n",
        "    x += 1 \n",
        "    x += 1\n",
        "    x += 1\n",
        "    return x"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "43BhH51m73Kf",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "outputId": "15f9b3b4-7623-4b56-cf49-e6a279954598"
      },
      "source": [
        "operations(x=1)"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "4"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 49
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dIorZSTM72h-"
      },
      "source": [
        "We were able to achieve what we want but we now increased the size of our `operations` function and if we want to do the same incrementing for any other function, we have to add the same code to all of those as well ... not very efficient. To solve this, let's create a decorator called `add` which increments `x` by 1 before and after the main function `f` executes."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "kvMbqzW5cikA"
      },
      "source": [
        "### Creating a decorator function"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RFReNETpci1M"
      },
      "source": [
        "The decorator function accepts a function `f` which is the function we wish to wrap around, in our case, it's `operations()`. The output of the decorator is its `wrapper` function which receives the arguments and keyword arguments passed to function `f`.\n",
        "\n",
        "Inside the `wrapper` function, we can:\n",
        "1. extract the input parameters passed to function `f`.\n",
        "2. make any changes we want to the function inputs.\n",
        "3. function `f` is executed\n",
        "4. make any changes to the function outputs\n",
        "5. `wrapper` function returns some value(s), which is what the decorator returns as well since it returns `wrapper`."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "6Lf7Ixrasiby"
      },
      "source": [
        "# Decorator\n",
        "def add(f):\n",
        "    def wrapper(*args, **kwargs):\n",
        "        \"\"\"Wrapper function for @add.\"\"\"\n",
        "        x = kwargs.pop('x') # .get() if not altering x\n",
        "        x += 1 # executes before function f\n",
        "        x = f(*args, **kwargs, x=x)\n",
        "        x += 1 # executes after function f\n",
        "        return x\n",
        "    return wrapper"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uVPWm6yfAmk9"
      },
      "source": [
        "We can use this decorator by simply adding it to the top of our main function preceded by the `@` symbol. "
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "CY2Vtxv9sih6"
      },
      "source": [
        "@add\n",
        "def operations(x):\n",
        "    \"\"\"Basic operations.\"\"\"\n",
        "    x += 1\n",
        "    return x"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "-BhmFvkJsifJ",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "outputId": "29c87f17-474a-4868-cd0a-dd4d4342946d"
      },
      "source": [
        "operations(x=1)"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "4"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 52
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rX4Ag4ryArQl"
      },
      "source": [
        "Suppose we wanted to debug and see what function actually executed with `operations()`. "
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "LsDkTSz6siZG",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "outputId": "6bc583c3-62d0-44b2-c3bc-7a6fb7e14671"
      },
      "source": [
        "operations.__name__, operations.__doc__"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "('wrapper', 'Wrapper function for @add.')"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 53
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "G9Rd1PrjA5d9"
      },
      "source": [
        "The function name and docstring are not what we're looking for but it appears this way because the `wrapper` function is what was executed. In order to fix this, Python offers `functools.wraps` which carries the main function's metadata. "
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "AtyQ8qb_upnc"
      },
      "source": [
        "from functools import wraps"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ggp95rl4uuKW"
      },
      "source": [
        "# Decorator\n",
        "def add(f):\n",
        "    @wraps(f)\n",
        "    def wrap(*args, **kwargs):\n",
        "        \"\"\"Wrapper function for @add.\"\"\"\n",
        "        x = kwargs.pop('x') \n",
        "        x += 1\n",
        "        x = f(*args, **kwargs, x=x)\n",
        "        x += 1\n",
        "        return x\n",
        "    return wrap"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "GvWdFV5XuuPn"
      },
      "source": [
        "@add\n",
        "def operations(x):\n",
        "    \"\"\"Basic operations.\"\"\"\n",
        "    x += 1\n",
        "    return x"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "90Za5Wb0uuNl",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "outputId": "1089cbb1-e1b7-4896-83ce-78a7aec14467"
      },
      "source": [
        "operations.__name__, operations.__doc__"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "('operations', 'Basic operations.')"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 57
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "aDR_uPXHBiIR"
      },
      "source": [
        "Awesome! We were able to decorate our main function `operation()` to achieve the customization we wanted without actually altering the function. We can reuse our decorator for other functions that may need the same customization!\n",
        "\n",
        "> This was a dummy example to show how decorators work but we'll be using them heavily during our [MLOps](https://madewithml.com/#mlops) lessons. A simple scenario would be using decorators to create uniform JSON responses from each API endpoint without including the bulky code in each endpoint."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "__HvzfLUB_qK"
      },
      "source": [
        "# Callbacks"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "46RnJdMZCDT3"
      },
      "source": [
        "Decorators allow for customized operations before and after the main function's execution but what about in between? Suppose we want to conditionally/situationally do some operations. Instead of writing a whole bunch of if-statements and make our functions bulky, we can use callbacks!\n",
        "\n",
        "*   **callbacks**: conditional/situational processing within the function."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_QEMtyB3FK34"
      },
      "source": [
        "Our callbacks will be classes that have functions with key names that will execute at various periods during the main function's execution. The function names are up to us but we need to invoke the same callback functions within our main function."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "YUfpcY11uplW"
      },
      "source": [
        "# Callback\n",
        "class x_tracker(object):\n",
        "    def __init__(self, x):\n",
        "        self.history = []\n",
        "    def at_start(self, x):\n",
        "        self.history.append(x)\n",
        "    def at_end(self, x):\n",
        "        self.history.append(x)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9hphXr9hC0Us"
      },
      "source": [
        "We can pass in as many callbacks as we want and because they have appropriately named functions, they will be invoked at the appropriate times. "
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "F3XFote_CzlA"
      },
      "source": [
        "def operations(x, callbacks=[]):\n",
        "    \"\"\"Basic operations.\"\"\"\n",
        "    for callback in callbacks:\n",
        "        callback.at_start(x)\n",
        "    x += 1\n",
        "    for callback in callbacks:\n",
        "        callback.at_end(x)\n",
        "    return x"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "d_QV5MZNDILI",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "outputId": "9726744e-80a1-4b82-e059-58597b1a7260"
      },
      "source": [
        "x = 1\n",
        "tracker = x_tracker(x=x)\n",
        "operations(x=x, callbacks=[tracker])"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "2"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 60
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "j0RZ90HoDMbx",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "outputId": "d02a8838-6862-480e-892d-8b1fe8166423"
      },
      "source": [
        "tracker.history"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[1, 2]"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 61
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ET1f4aleDYWZ"
      },
      "source": [
        "# Putting it all together"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "V52b86DxDbux"
      },
      "source": [
        "decorators + callbacks = powerful customization *before*, *during* and *after* the main function’s execution without increasing its complexity. We will be using this duo to create powerful ML training scripts that are highly customizable in future lessons."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "BEeYY6QHEsE-"
      },
      "source": [
        "from functools import wraps"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "NPcjeVOkEsLC"
      },
      "source": [
        "# Decorator\n",
        "def add(f):\n",
        "    @wraps(f)\n",
        "    def wrap(*args, **kwargs):\n",
        "        \"\"\"Wrapper function for @add.\"\"\"\n",
        "        x = kwargs.pop('x') # .get() if not altering x\n",
        "        x += 1 # executes before function f\n",
        "        x = f(*args, **kwargs, x=x)\n",
        "        # can do things post function f as well\n",
        "        return x\n",
        "    return wrap"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "rBPVInWfEsIk"
      },
      "source": [
        "# Callback\n",
        "class x_tracker(object):\n",
        "    def __init__(self, x):\n",
        "        self.history = [x]\n",
        "    def at_start(self, x):\n",
        "        self.history.append(x)\n",
        "    def at_end(self, x):\n",
        "        self.history.append(x)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Px0vMT9gFcyp"
      },
      "source": [
        "# Main function\n",
        "@add\n",
        "def operations(x, callbacks=[]):\n",
        "    \"\"\"Basic operations.\"\"\"\n",
        "    for callback in callbacks:\n",
        "        callback.at_start(x)\n",
        "    x += 1\n",
        "    for callback in callbacks:\n",
        "        callback.at_end(x)\n",
        "    return x"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "0AhGDNPWFc4i",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "outputId": "144f2d3d-5b5e-454d-e4a0-5f85937c0e2b"
      },
      "source": [
        "x = 1\n",
        "tracker = x_tracker(x=x)\n",
        "operations(x=x, callbacks=[tracker])"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "3"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 66
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ajyUN2y0Fc2E",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "outputId": "10960ba0-5a00-450b-a27d-854352ccb476"
      },
      "source": [
        "tracker.history"
      ],
      "execution_count": null,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "[1, 2, 3]"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 67
        }
      ]
    }
  ]
}